/** This class makes all xml-parsing jobs 
 *  @author ChrM */

package com.jtalker.server;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Parser who parse all the message between server and client.
 * 
 * @author Administrator
 * 
 */
public class JTalkerParser extends DefaultHandler {

	/**
	 * MEMBER tag includes some members informations. This tag provides follows
	 * properties: NAME [, STATUS, PASSWD]
	 */
	public static final String MEMBER = "MEMBER";
	public static final String MEMBER_NAME = "name";
	public static final String MEMBER_STATUS = "status";
	public static final String MEMBER_PASSWD = "passwd";
	public static final String MEMBER_IP = "ip";
	public static final String MEMBER_TYPE = "type";
	public static final String MEMBER_SID = "sid";

	/** NEW_MEMBER tag for registered users */
	public static final String NEW_MEMBER = "NEW_MEMBER";

	/**
	 * MESSAGE tag includes some messages. This tag provides follows properties:
	 * NAME, CONTENT, SENDER, STATUS, TO, TYPE
	 */
	public static final String MESSAGE = "MESSAGE";
	public static final String MESSAGE_CONTENT = "content";
	public static final String MESSAGE_SENDER = "sender";
	public static final String MESSAGE_STATUS = "status";
	public static final String MESSAGE_TO = "to";
	public static final String MESSAGE_TYPE = "type";
	public static final String MESSAGE_SUBJECT = "subject";
	public static final String MESSAGE_ID = "id";

	/** Old style server messages: Error or Info */
	public static final String ERROR = "ERROR";

	/** GLOBAL_USER tag for registered users */
	public static final String GLOBAL_USER = "GLOBAL_USER";

	/** Constructor */
	public JTalkerParser() {
	}

	/** Starts parsing */
	public boolean parsing(String xmlStream) {
		// reset
		this.listMembers.clear(); // 清除上次解析的用户.
		this.listMessages.clear(); // 清除上次解析的消息.
		this.lastError = null;
		this.isNewMember = false;
		this.lastCommand = null;
		this.lastCommandID = null;

		try {
			SAXParserFactory fct = SAXParserFactory.newInstance();
			fct.setValidating(false);
			SAXParser saxParser = fct.newSAXParser();
			InputStream is = new ByteArrayInputStream(
					xmlStream.getBytes("UTF-8"));
			saxParser.parse(is, this);
		} catch (Exception _e) {
			System.out.println("***: " + _e.toString());
			return false;
		}

		return true;
	}

	/** Starts parsing of file */
	public boolean parsing(File globalUsers) {
		// reset
		this.lastError = null;
		this.isNewMember = false;

		if (!globalUsers.canRead()) {
			this.lastError = "Can't load file";
			return false;
		}

		try {
			SAXParserFactory fct = SAXParserFactory.newInstance();
			fct.setValidating(false);
			SAXParser saxParser = fct.newSAXParser();
			saxParser.parse(globalUsers, this);
		} catch (Exception e) {
			// e.printStackTrace();
			return false;
		}

		return true;
	}

	// ---------------------------------------------------------------
	/** Gets the content of the message message */
	public String getMessage() {
		// return this.messageContent;
		if (this.getAMessage(0) != null) {
			return this.getAMessage(0).content;
		} else {
			return null;
		}
	}

	/** Gets the type of the Message */
	public String getMessageType() {
		if (this.getAMessage(0) != null) {
			return this.getAMessage(0).messageType;
		} else {
			return null;
		}
	}

	/** Gets the ID of the Message */
	public String getMessageID() {
		if (this.getAMessage(0) != null){
			return this.getAMessage(0).messageID;
		}else{
			return null;
		}
	}

	/** Gets the subject of the Message */
	public String getMessageSubject() {
		if (this.getAMessage(0) != null)
			return this.getAMessage(0).messageSubject;
		else
			return null;
	}

	/** Gets the first Sender (on server site) */
	public String getSender() {
		// return this.messageSender;
		if (this.getAMessage(0) != null)
			return this.getAMessage(0).sender;
		else
			return null;
	}

	/** Gets the first sender status (on server site; in the message Tag) */
	public String getStatus() {
		// return this.messageSenderStatus;
		if (this.getAMessage(0) != null)
			return this.getAMessage(0).senderStatus;
		else
			return null;
	}

	/**
	 * Gets the first sender status (in the member Tag). Should be used only for
	 * update the own status!
	 */
	public String getMemberStatus() {
		if (this.listMembers.size() == 1) {
			return this.listMembers.get(0).status;
		} else {
			return null;
		}
	}

	/**
	 * Gets a list of Receivers for the first message (separated by double
	 * semikolon: ';;')
	 */
	public String getRecs() {
		// return this.messageReceivers;
		if (this.getAMessage(0) != null) {
			return this.getAMessage(0).messageReceivers;
		} else {
			return null;
		}
	}

	/** Gets the recieved command */
	public String getCommand() {
		return this.lastCommand;
	}

	/** Gets recieved commands ID */
	public String getCommandID() {
		return this.lastCommandID;
	}

	/** Gets a member by number */
	public OneMember getAMember(int index) {
		try {
			return this.listMembers.get(index);
		} catch (Exception e) {
			return null;
		}
	}

	/** Gets a message by number */
	public OneMessage getAMessage(int index) {
		try {
			return this.listMessages.get(index);
		} catch (Exception e) {
			return null;
		}
	}

	/** Gets a global user by number */
	public GlobalUser getAGlobalUser(int index) {
		try {
			return this.listGlobalUsers.get(index);
		} catch (Exception e) {
			return null;
		}
	}

	/** Gets number of members */
	public int getMemberNum() {
		return this.listMembers.size();
	}

	/** Gets number of messages */
	public int getMessagesNum() {
		return this.listMessages.size();
	}

	/** Gets number of global users */
	public int getGlobalUserNum() {
		return this.listGlobalUsers.size();
	}

	/** Gets the last Error */
	public String getError() {
		return this.lastError;
	}

	/** Is this a accout-creation? */
	public boolean isNewMember() {
		return this.isNewMember;
	}

	/** Splits Recs to an ArrayList and get it */
	public String[] getRecsAsArray(int _index) {
		return this.getAMessage(_index).messageReceivers.split(";;");
	}

	/** Splits Recs to an ArrayList and get it (for the first message) */
	public String[] getRecsAsArray() {
		return this.getAMessage(0).messageReceivers.split(";;");
	}

	// ---------------------------------------------------------------

	/** SAX-methods */
	public void startElement(String _nsURI, String _sName, String _qName,
			Attributes _attrs) {
		this.currentTag = _qName;

		if (this.currentTag.equalsIgnoreCase(MESSAGE)) {
			// check message type
			String tmpType = _attrs.getValue(MESSAGE_TYPE);
			if (tmpType != null && tmpType.equalsIgnoreCase("error")) {
				this.lastError = _attrs.getValue(MESSAGE_CONTENT);
			} else if (tmpType != null && tmpType.equalsIgnoreCase("command")
					&& _attrs.getValue(MESSAGE_TO) == null) {
				this.lastCommand = _attrs.getValue(MESSAGE_CONTENT);
				this.lastCommandID = _attrs.getValue(MESSAGE_ID);
			} else {
				this.listMessages.add(new OneMessage(_attrs
						.getValue(MESSAGE_CONTENT), _attrs
						.getValue(MESSAGE_SENDER), _attrs
						.getValue(MESSAGE_STATUS), _attrs.getValue(MESSAGE_TO),
						tmpType, _attrs.getValue(MESSAGE_SUBJECT), _attrs
								.getValue(MESSAGE_ID)));
			}

		} else if (this.currentTag.equalsIgnoreCase(MEMBER)) {
			// A Member list is received
			this.listMembers.add(new OneMember(_attrs.getValue(MEMBER_NAME),
					_attrs.getValue(MEMBER_STATUS), _attrs
							.getValue(MEMBER_PASSWD), _attrs
							.getValue(MEMBER_IP), _attrs.getValue(MEMBER_TYPE),
					_attrs.getValue(MEMBER_SID)));
		} else if (this.currentTag.equalsIgnoreCase(ERROR)) {
			// OBSOLETE: An Error occured (from Client)
			this.lastError = _attrs.getValue("message");
			// } else if(this.currentTag.equalsIgnoreCase(this.INFO)) {
			// OBSOLETE: Server sends an info message
		} else if (this.currentTag.equalsIgnoreCase(GLOBAL_USER)) {
			// A Global User list is parsed from a File
			GlobalUser globalUser = new GlobalUser(_attrs.getValue("name"),
					_attrs.getValue("passwd"), _attrs.getValue("role"),
					_attrs.getValue("contacts"), false);
			this.listGlobalUsers.add(globalUser);
		} else if (this.currentTag.equalsIgnoreCase(NEW_MEMBER)) {
			// New Member try to register..
			this.isNewMember = true;
			this.listMembers.add(new OneMember(_attrs.getValue(MEMBER_NAME),
					null, _attrs.getValue(MEMBER_PASSWD), null, null, null));
		}
	}

	/** SAX-methods */
	public void characters(char[] ch, int start, int length) {
	}

	/** SAX-methods */
	public void endElement(String _uri, String _localName, String _qName) {
	}

	/** SAX-error-methods */
	public void error(SAXParseException _e) {
		System.out.println("*** SAX-Parser Error: " + _e.toString());
	}

	/** SAX-error-methods */
	public void fatalError(SAXParseException _e) {
		System.out.println("*** SAX-Parser Error: " + _e.toString());
	}

	// private members
	private String currentTag = null;

	private String lastError = null;
	private String lastCommand = null;
	private String lastCommandID = null;
	private boolean isNewMember = false;
	// private String messageContent = null;
	// private String messageSender = null;
	// private String messageSenderStatus = null;
	// private String messageReceivers = null;
	private ArrayList<OneMember> listMembers = new ArrayList<OneMember>();
	private ArrayList<OneMessage> listMessages = new ArrayList<OneMessage>();
	private ArrayList<GlobalUser> listGlobalUsers = new ArrayList<GlobalUser>();

	/** This inner class represents a member */
	public class OneMember {
		public OneMember(String name, String status, String passwd, String ip,
				String type, String sid) {
			this.name = name;
			if (status != null) {
				this.status = status;
			} else {
				this.status = "normal";
			}
			this.passwd = passwd;
			this.ip = ip;
			if (type != null) {
				this.type = type;
			} else {
				this.type = JTalkerMember.USER_TYPE_PERSISTENT;
			}
			this.sid = sid;
		}

		/** like "UnserA" */
		public String name = null;
		/** "normal", "busy", "robot" */
		public String status = null;
		/** "xxx" */
		public String passwd = null;
		/** "127.0.0.1" */
		public String ip = null;
		/** web-client,persistent,chat-room */
		public String type = JTalkerMember.USER_TYPE_PERSISTENT;
		/** Session ID */
		public String sid = null;
	}

	/** This inner class represents a message */
	public class OneMessage {
		public OneMessage(String content, String sender, String senderStatus,
				String messageReceivers, String messageType,
				String messageSubject, String messageID) {
			this.content = content;
			this.sender = sender;
			this.senderStatus = senderStatus;
			this.messageReceivers = messageReceivers;
			this.messageType = messageType;
			this.messageSubject = messageSubject;
			this.messageID = messageID;
		}

		/** Text */
		public String content = null;
		/** Member name */
		public String sender = null;
		/** "normal", "busy", "robot" */
		public String senderStatus = null;
		/** "UserA;;UserB;;UserC" */
		public String messageReceivers = null;
		/** "normal", "error", "info", "command" */
		public String messageType = null;
		/** "picture.jpg" */
		public String messageSubject = null;
		/** "xyz123" */
		public String messageID = null;
	}

	/** Method for simple using the internal class objects */
	public GlobalUser createGlobalUserObject(String name, String passwd,
			String role, String[] contacts) {
		return new GlobalUser(name, passwd, role, contacts, true);
	}

	/** This inner class represents a global user on the server */
	public class GlobalUser {
		public static final String USER = "user";
		public static final String ADMIN = "admin";
		public static final String ROOT = "root";
		public static final String CONTACTS_ALL = "*";

		/** Standard C'tor */
		public GlobalUser(String name, String passwd, String role,
				boolean makeHash) {
			this.name = name;
			// Create hash only, if no reading from user file!
			if (!makeHash) {
				this.passwd = passwd;
			} else {
				this.passwd = JTalkerServer.makeHash(passwd);
			}
			this.setRole(role);
		}

		/** C'tor. Contacts as array */
		public GlobalUser(String name, String passwd, String role,
				String[] contacts, boolean makeHash) {
			this(name, passwd, role, makeHash);
			this.setContacts(contacts);
		}

		/** C'tor. Contacts as ;; List */
		public GlobalUser(String name, String passwd, String role,
				String contacts, boolean makeHash) {
			this(name, passwd, role, makeHash);
			this.contactList = contacts;
		}

		/** Sets new Role */
		private boolean setRole(String newRole) {
			if (newRole == null) {
				return false;
			}
			if (newRole.equals(USER) || newRole.equals(ADMIN)
					|| newRole.equals(ROOT)) {
				this.role = newRole;
				return true;
			} else {
				return false;
			}
		}

		/** Gets users role */
		public String getRole() {
			return this.role;
		}

		/** Sets contacts */
		public boolean setContacts(String[] contacts) {
			if (contacts == null || contacts.length == 0) {
				return false;
			}
			this.contactList = "";
			for (int i = 0; i < contacts.length; i++) {
				this.contactList += (contacts[i] + ";;");
			}
			return true;
		}

		/** Adds a new contact */
		public boolean addContact(String contact) {
			if (contact == null) {
				return false;
			}
			this.contactList += (contact + ";;");
			return true;
		}

		/** Gets users contacts as ";;" separated string */
		public String getContacts() {
			return this.contactList;
		}

		/** Gets the name */
		public String getName() {
			return name;
		}

		/** Gets passwd */
		public String getPasswd() {
			return passwd;
		}

		private String name = null;
		private String passwd = null;
		/** Users Role like "ROOT" "ADMIN" "USER" */
		private String role = GlobalUser.USER;
		/**
		 * ";;"-separated list of contacts like "hansi;;joe;;tux" or "*" for all
		 */
		private String contactList = GlobalUser.CONTACTS_ALL;
	}
}
